#include "upload.h"
#include "log.h"
#include "util.h"
#include "config.h"
#include "kmysql.h"
#include <fcntl.h>
#include <unistd.h>
#include <stdlib.h>
#include <string.h>
#include <mysql/mysql.h>
#include <fcgi_stdio.h>

// mysql 数据库配置信息 用户名， 密码， 数据库名称
static char mysqlUser[128] = {0};
static char mysqlPwd[128] = {0};
static char mysqlDb[128] = {0};

void reddMySqlConfig()
{
    LOG(UPLOAD_LOG_MODULE, UPLOAD_LOG_PROC, "reddMySqlConfig\n");
    GetConfigValue(CONFIG_PATH, "mysql", "user", mysqlUser);
    GetConfigValue(CONFIG_PATH, "mysql", "password", mysqlPwd);
    GetConfigValue(CONFIG_PATH, "mysql", "database", mysqlDb);
    LOG(UPLOAD_LOG_MODULE, UPLOAD_LOG_PROC, "mysql配置信息：user=%s, password=%s, database=%s\n", mysqlUser, mysqlPwd, mysqlDb);
}

void GetContentDispositionStrValue(char *buffer, char *name, char *value)
{
    LOG(UPLOAD_LOG_MODULE, UPLOAD_LOG_PROC, "GetContentDispositionStrValue\n");
    // 3. 获取user
    char *p = buffer;
    char *q = strstr(p, name);
    if(NULL == q)
    {
        LOG(UPLOAD_LOG_MODULE, UPLOAD_LOG_PROC, "wrong no %s\n", name);
        return;
    }
    
    q += strlen(name);
    q++; // 跳过"
    char *k = strstr(q, "\"");
    strncpy(value, q, k - q);
    LOG(UPLOAD_LOG_MODULE, UPLOAD_LOG_PROC, "%s[%s]\n", name, value);
    TrimSpace(value);
}

void GetContent(char *buffer, char *key, char *value)
{
    LOG(UPLOAD_LOG_MODULE, UPLOAD_LOG_PROC, "GetContent\n");
    char *q = strstr(buffer, key);
    if (NULL == q)
    {
        LOG(UPLOAD_LOG_MODULE, UPLOAD_LOG_PROC, "wrong no %s\n", key);
        return;
    }
    q = strstr(buffer, "\r\n\r\n");
    if (NULL == q)
    {
        LOG(UPLOAD_LOG_MODULE, UPLOAD_LOG_PROC, "wrong no %s\n", key);
        return;
    }
    q += 4; // \r\n\r\n
    char *k = strstr(q, "\r\n");
    if (NULL == k)
    {
        LOG(UPLOAD_LOG_MODULE, UPLOAD_LOG_PROC, "wrong no %s\n", key);
        return;
    }
    strncpy(value, q, k - q);
    value[k - q] = '\0'; // 结束符
    LOG(UPLOAD_LOG_MODULE, UPLOAD_LOG_PROC, "%s[%s]\n", key, value);
    TrimSpace(value);
}

int SaveToFile(char *filename, char *buffer, int len)
{
    int ret = 0;
    LOG(UPLOAD_LOG_MODULE, UPLOAD_LOG_PROC, "SaveToFile\n");
    // 保存文件
    int fd = open(filename, O_CREAT | O_WRONLY, 0644);
    if (fd < 0)
    {
        LOG(UPLOAD_LOG_MODULE, UPLOAD_LOG_PROC, "open %s error\n", filename);
        return  -1;
    }
    int fileSize = len;
    LOG(UPLOAD_LOG_MODULE, UPLOAD_LOG_PROC, "open %s success, len is %d\n", filename, fileSize);

    ftruncate(fd, fileSize);
    write(fd, buffer, fileSize);
    close(fd);
}

int RecvSaveFile(long len, char *user, char *filename, char *md5, char *token, long *size)
{
    LOG(UPLOAD_LOG_MODULE, UPLOAD_LOG_PROC, "RecvSaveFile\n");

    int ret = 0;
    char *begin = NULL;
    char *p = NULL;
    char *q = NULL;
    char *k = NULL;

    char contentText[TEMP_BUF_MAX_LEN] = {0}; // 文件头部信息
    char boundary[TEMP_BUF_MAX_LEN] = {0};    // 分界线信息

    /*****************开辟存放文件的内存*******************/
    char *fileBuf = (char *)MALLOC(len);
    if (NULL == fileBuf)
    {
        LOG(UPLOAD_LOG_MODULE, UPLOAD_LOG_PROC, "malloc error\n");
        return -1;
    }

    do
    {
        // 读取文件内容
        int ret2 = fread(fileBuf, 1, len, stdin);
        if (0 == ret2)
        {
            LOG(UPLOAD_LOG_MODULE, UPLOAD_LOG_PROC, "fread(fileBuf, 1, len, stdin) error\n");
            ret = -1;
            break;
        }
        LOG(UPLOAD_LOG_MODULE, UPLOAD_LOG_PROC, "fread buffer is \n%s\n", fileBuf);
        /*****************处理post请求*******************/
        /*
        ----------------------------285398485232703411676346
        Content-Disposition: form-data; name="file"; filename="新建文本文档.txt"; filename*=UTF-8''%E6%96%B0%E5%BB%BA%E6%96%87%E6%9C%AC%E6%96%87%E6%A1%A3.txt
        Content-Type: text/plain

        钛盘
        ----------------------------285398485232703411676346
        Content-Disposition: form-data; name="user"

        liwan
        ----------------------------285398485232703411676346
        Content-Disposition: form-data; name="token"

        995bdadc07e791db89bdc44077e575e8
        ----------------------------285398485232703411676346
        Content-Disposition: form-data; name="md5"

        995bdadc07e791db89bdc44077e575e8
        ----------------------------285398485232703411676346--
       */
        // 1. 获取分界线
        begin = fileBuf;
        p = strstr(begin, "\r\n");
        if (NULL == p)
        {
            LOG(UPLOAD_LOG_MODULE, UPLOAD_LOG_PROC, "wrong not boundary\n");
            ret = -1;
            break;
        }

        strncpy(boundary, begin, p - begin);
        boundary[p - begin] = '\0'; // 结束符
        LOG(UPLOAD_LOG_MODULE, UPLOAD_LOG_PROC, "boundary:[%s]\n", boundary);

        p += 2; // \r\n
        len -= (p - begin);
        begin = p;

        // 2. 获取Content-Disposition
        p = strstr(begin, "\r\n");
        if (NULL == p)
        {
            LOG(UPLOAD_LOG_MODULE, UPLOAD_LOG_PROC, "wrong no content text， not found Content-Disposition\n");
            ret = -1;
            break;
        }

        strncpy(contentText, begin, p - begin);
        contentText[p - begin] = '\0'; // 结束符
        LOG(UPLOAD_LOG_MODULE, UPLOAD_LOG_PROC, "contentText:[%s]\n", contentText);
        p += 2;
        len -= (p - begin);

        // 获取文件名
        GetContentDispositionStrValue(contentText, "filename=", filename);
        if (strlen(filename) == 0)
        {
            LOG(UPLOAD_LOG_MODULE, UPLOAD_LOG_PROC, "wrong no filename\n");
            ret = -1;
            break;
        }

        // 文件另存为
        begin = strstr(p, "\r\n\r\n");
        if (NULL == begin)
        {
            LOG(UPLOAD_LOG_MODULE, UPLOAD_LOG_PROC, "wrong no file content\n");
            ret = -1;
            break;
        }
        begin += 4; // \r\n\r\n
        p = strstr(begin, boundary);
        if (NULL == p)
        {
            LOG(UPLOAD_LOG_MODULE, UPLOAD_LOG_PROC, "wrong no file content\n");
            ret = -1;
            break;
        }
        int fileSize = p - begin - 2; // -2是因为有\r\n
        SaveToFile(filename, begin, fileSize);
        LOG(UPLOAD_LOG_MODULE, UPLOAD_LOG_PROC, "save file success, fileSize is %d\n", fileSize);

        // 获取user
        begin = strstr(p, boundary);;
        GetContent(begin, "user", user);
        if (strlen(user) == 0)
        {
            LOG(UPLOAD_LOG_MODULE, UPLOAD_LOG_PROC, "wrong no user\n");
            ret = -1;
            break;
        }

        // 获取token
        p += strlen(boundary);
        begin = strstr(p, boundary);
        GetContent(begin, "token", token);
        if (strlen(token) == 0)
        {
            LOG(UPLOAD_LOG_MODULE, UPLOAD_LOG_PROC, "wrong no token\n");
            ret = -1;
            break;
        }

        // 获取md5
        p += strlen(boundary);
        begin = strstr(p, boundary);
        GetContent(begin, "md5", md5);
        if (strlen(md5) == 0)
        {
            LOG(UPLOAD_LOG_MODULE, UPLOAD_LOG_PROC, "wrong no md5\n");
            ret = -1;
            break;
        }
    } while (0);

    if (fileBuf != NULL)
    {
        FREE(fileBuf);
    }
    return ret;
}

int UploadToFastDfs(char *filename, char *fileId)
{
    LOG(UPLOAD_LOG_MODULE, UPLOAD_LOG_PROC, "UploadToFastDfs\n");
    if (NULL == filename || NULL == fileId)
    {
        return -1;
    }

    if (strcmp(filename, "") == 0)
    {
        return -1;
    }

    int ret = 0;
    int fd[2];

    // 创建管道
    if (pipe(fd) < 0)
    {
        LOG(UPLOAD_LOG_MODULE, UPLOAD_LOG_PROC, "pipe error\n");
        return -1;
    }

    pid_t pid = fork();
    if (pid < 0)
    {
        LOG(UPLOAD_LOG_MODULE, UPLOAD_LOG_PROC, "fork error\n");
        return -1;
    }

    if (0 == pid)
    {
        // 子进程用来执行cmd命令
        close(fd[0]);
        dup2(fd[1], STDOUT_FILENO);
        close(fd[1]);

        // 读取配置文件路径
        char fdfsCliConfPath[256] = {0};
        GetConfigValue(CONFIG_PATH, "dfs_path", "client", fdfsCliConfPath);
        LOG(UPLOAD_LOG_MODULE, UPLOAD_LOG_PROC, "fdfsCliConfPath=[%s]\n", fdfsCliConfPath);
        // 使用命令上传文件，返回id写入到管道中
        execlp("fdfs_upload_file", "fdfs_upload_file", fdfsCliConfPath, filename, NULL);

        // 执行失败
        LOG(UPLOAD_LOG_MODULE, UPLOAD_LOG_PROC, "execlp fdfs_upload_file error\n");
        close(fd[1]);
        exit(1);
    }
    else
    {
        // 父进程用来读取子进程的输出
        close(fd[1]);
        read(fd[0], fileId, TEMP_BUF_MAX_LEN);

        // 去掉fileId中的换行符
        TrimSpace(fileId);
        if (strcmp(fileId, "") == 0)
        {
            LOG(UPLOAD_LOG_MODULE, UPLOAD_LOG_PROC, "upload file error\n");
            ret = -1;
        }
        LOG(UPLOAD_LOG_MODULE, UPLOAD_LOG_PROC, "fileId=[%s]\n", fileId);

        // 等待子进程结束，避免僵尸进程
        wait(NULL);
        close(fd[0]);
    }

    return ret;
}

int MakeFileUrl(char *fileId, char *fdfsFileUrl)
{
    LOG(UPLOAD_LOG_MODULE, UPLOAD_LOG_PROC, "MakeFileUrl\n");
    int ret = 0;
    char fdfsFileStatBuf[TEMP_BUF_MAX_LEN] = {0};
    char fdfsFileHostName[HOST_NAME_LEN] = {0}; // storage的IP

    int fd[2];
    if (pipe(fd) < 0)
    {
        LOG(UPLOAD_LOG_MODULE, UPLOAD_LOG_PROC, "pipe error\n");
        return -1;
    }

    pid_t pid = fork();
    if (pid < 0)
    {
        LOG(UPLOAD_LOG_MODULE, UPLOAD_LOG_PROC, "fork error\n");
        return -1;
    }

    if (0 == pid)
    {
        // 子进程用来获取IP
        close(fd[0]);
        // 　重定向写管道
        dup2(fd[1], STDOUT_FILENO);
        close(fd[1]);

        char fdfsCliConfPath[256] = {0};
        GetConfigValue(CONFIG_PATH, "dfs_path", "client", fdfsCliConfPath);
        LOG(UPLOAD_LOG_MODULE, UPLOAD_LOG_PROC, "fdfsCliConfPath=[%s]\n", fdfsCliConfPath);

        execlp("fdfs_file_info", "fdfs_file_info", fdfsCliConfPath, fileId, NULL);

        // 执行失败
        LOG(UPLOAD_LOG_MODULE, UPLOAD_LOG_PROC, "execlp fdfs_file_info error\n");
        exit(1);
    }
    else
    {
        // 父进程用来拼接HTTP
        close(fd[1]);
        read(fd[0], fdfsFileStatBuf, TEMP_BUF_MAX_LEN);

        wait(NULL);
        close(fd[0]);

        /*
        source storage id: 0
        source ip address: 192.168.1.101
        file create timestamp: 2023-10-01 12:34:56
        file size: 123456
        file crc32: 12345678
        */
        char *p = strstr(fdfsFileStatBuf, "source ip address: ");
        char *q = p + strlen("source ip address: ");
        char *k = strstr(q, "\n");
        strncpy(fdfsFileHostName, q, k - q);
        fdfsFileHostName[k - q] = '\0';

        // 读取端口信息
        char storagePort[8] = {0};
        GetConfigValue(CONFIG_PATH, "storage_web_server", "port", storagePort);
        strcat(fdfsFileUrl, "http://");
        strcat(fdfsFileUrl, fdfsFileHostName);
        strcat(fdfsFileUrl, ":");
        strcat(fdfsFileUrl, storagePort);
        strcat(fdfsFileUrl, "/");
        strcat(fdfsFileUrl, fileId);
        LOG(UPLOAD_LOG_MODULE, UPLOAD_LOG_PROC, "fdfsFileUrl=[%s]\n", fdfsFileUrl);
    }

    return ret;
}

int InsertFileInfoToMysql(char *user, char *filename, char *md5, long size, char *fdfsFileUrl)
{
    LOG(UPLOAD_LOG_MODULE, UPLOAD_LOG_PROC, "InsertFileInfoToMysql\n");
    int ret = 0;
    MYSQL *conn = NULL;
    conn = MysqlConn(mysqlUser, mysqlPwd, mysqlDb);
    if (conn == NULL)
    {
        LOG(UPLOAD_LOG_MODULE, UPLOAD_LOG_PROC, "msql_conn error\n");
        return -1;
    }
    mysql_query(conn, "set names utf8");

    do
    {
        // 插入file_info表
        // 1. 判断文件是否已经存在
        // 2. 如果存在，更新count字段
        // 3. 如果不存在，插入一条记录
        char sqlCmd[SQL_MAX_LEN] = {0};
        sprintf(sqlCmd, "select count from file_info where md5 = '%s'", md5);
        LOG(UPLOAD_LOG_MODULE, UPLOAD_LOG_PROC, "sqlCmd=[%s]\n", sqlCmd);
        char temp[TEMP_BUF_MAX_LEN] = {0};
        int ret2 = ProcessResultOne(conn, sqlCmd, temp, 0);
        if (ret2 == 1)
        {
            char suffix[SUFFIX_LEN] = {0};
            GetFileSuffix(filename, suffix);
            sprintf(sqlCmd, "insert into file_info (md5, file_id, url, size, type, count) values ('%s', '%s', '%s', %ld, '%s', %d)", md5, filename, fdfsFileUrl, size, suffix, 1);
            LOG(UPLOAD_LOG_MODULE, UPLOAD_LOG_PROC, "sqlCmd=[%s]\n", sqlCmd);
            if (mysql_query(conn, sqlCmd) != 0)
            {
                LOG(UPLOAD_LOG_MODULE, UPLOAD_LOG_PROC, "insert file_info error\n");
                ret = -1;
                break;
            }
            LOG(UPLOAD_LOG_MODULE, UPLOAD_LOG_PROC, "insert file_info success\n");
        }
        else if (ret == 0)
        {
            int count = atoi(temp);
            count++;
            memset(sqlCmd, 0, SQL_MAX_LEN);
            sprintf(sqlCmd, "update file_info set count = %d where md5 = '%s'", count, md5);
            LOG(UPLOAD_LOG_MODULE, UPLOAD_LOG_PROC, "sqlCmd=[%s]\n", sqlCmd);
            if (mysql_query(conn, sqlCmd) != 0)
            {
                LOG(UPLOAD_LOG_MODULE, UPLOAD_LOG_PROC, "update file_info error\n");
                ret = -1;
                break;
            }
        }
        else
        {
            LOG(UPLOAD_LOG_MODULE, UPLOAD_LOG_PROC, "ProcessResultOne error\n");
            ret = -1;
            break;
        }

        // 插入user_file_list表
        time_t now;
        now = time(NULL);
        char createTime[TIME_STRING_LEN];
        strftime(createTime, TIME_STRING_LEN - 1, "%Y-%m-%d %H:%M:%S", localtime(&now));
        memset(sqlCmd, 0, SQL_MAX_LEN);
        sprintf(sqlCmd, "insert into user_file_list (user, md5, file_name, create_time, shared_status, pv) values ('%s', '%s', '%s', '%s', %d, %d)", user, md5, filename, createTime, 0, 0);
        LOG(UPLOAD_LOG_MODULE, UPLOAD_LOG_PROC, "sqlCmd=[%s]\n", sqlCmd);
        if (mysql_query(conn, sqlCmd) != 0)
        {
            LOG(UPLOAD_LOG_MODULE, UPLOAD_LOG_PROC, "insert user_file_list error\n");
            ret = -1;
            break;
        }

        // 更新user_file_count表
        // 1. 判断user_file_count表中是否有记录
        // 2. 如果有记录，更新count字段
        // 3. 如果没有记录，插入一条记录
        memset(sqlCmd, 0, SQL_MAX_LEN);
        memset(temp, 0, TEMP_BUF_MAX_LEN);
        sprintf(sqlCmd, "select count from user_file_count where user = '%s'", user);
        LOG(UPLOAD_LOG_MODULE, UPLOAD_LOG_PROC, "sqlCmd=[%s]\n", sqlCmd);
        ret2 = ProcessResultOne(conn, sqlCmd, temp, 0);
        // -1 失败  0 有记录，读取数据 1 没有记录 2 有记录，不读取数据
        memset(sqlCmd, 0, SQL_MAX_LEN);
        if (ret2 == 1)
        {
            sprintf(sqlCmd, "insert into user_file_count (user, count) values('%s', %d)", user, 1);
        }
        else if (ret2 == 0)
        {
            int count = atoi(temp);
            count++;
            sprintf(sqlCmd, "update user_file_count set count = %d where user = '%s'", count, user);
        }
        else
        {
            LOG(UPLOAD_LOG_MODULE, UPLOAD_LOG_PROC, "ProcessResultOne error\n");
            ret = -1;
            break;
        }

        LOG(UPLOAD_LOG_MODULE, UPLOAD_LOG_PROC, "sqlCmd=[%s]\n", sqlCmd);
        if (mysql_query(conn, sqlCmd) != 0)
        {
            LOG(UPLOAD_LOG_MODULE, UPLOAD_LOG_PROC, "insert user_file_count error\n");
            ret = -1;
            break;
        }
    } while (0);

    if (conn != NULL)
    {
        mysql_close(conn);
        conn = NULL;
    }
    return ret;
}

int main()
{
    char filename[FILE_NAME_LEN] = {0};
    char user[USER_NAME_LEN] = {0};
    char md5[MD5_LEN] = {0};
    char token[TOKEN_LEN] = {0};
    long fileSize = 0;
    // 文件上传到fastDFS后的文件id
    char fileId[TEMP_BUF_MAX_LEN] = {0};
    // 文件所存放storage的host_name
    char fdfsFileUrl[FILE_URL_LEN] = {0};

    reddMySqlConfig();

    while (FCGI_Accept() >= 0)
    {
        char *contentLength = getenv("CONTENT_LENGTH");
        int len = 0;
        int ret = 0;

        printf("Content-type: text/html\r\n\r\n");
        if (NULL != contentLength)
        {
            LOG(UPLOAD_LOG_MODULE, UPLOAD_LOG_PROC, "contentLength=%s\n", contentLength);
            len = strtol(contentLength, NULL, 10);
        }

        if (len <= 0)
        {
            printf("No data from standard input\n");
            LOG(UPLOAD_LOG_MODULE, UPLOAD_LOG_PROC, "len = 0, No data from standard input\n");
            ret = -1;
            continue;
        }

        do
        {
            ret = RecvSaveFile(len, user, filename, md5, token, &fileSize);
            if (ret < 0)
            {
                LOG(UPLOAD_LOG_MODULE, UPLOAD_LOG_PROC, "RecvSaveFile error\n");
                break;
            }
            LOG(UPLOAD_LOG_MODULE, UPLOAD_LOG_PROC, "%s RecvSaveFile success[%s, size %ld, md5 %s] in local\n", user, filename, fileSize, md5);

            // 上传文件到fastDFS
            ret = UploadToFastDfs(filename, fileId);
            if (ret < 0)
            {
                LOG(UPLOAD_LOG_MODULE, UPLOAD_LOG_PROC, "UploadToFastDfs error\n");
                break;
            }

            // 删除本地存放的文件
            unlink(filename);

            // 将fileId组URL返回给前端
            ret = MakeFileUrl(fileId, fdfsFileUrl);
            if (ret < 0)
            {
                LOG(UPLOAD_LOG_MODULE, UPLOAD_LOG_PROC, "MakeFileUrl error\n");
                break;
            }

            // 将文件信息写入数据库
            ret = InsertFileInfoToMysql(user, filename, md5, fileSize, fdfsFileUrl);
            if(ret < 0)
            {
                LOG(UPLOAD_LOG_MODULE, UPLOAD_LOG_PROC, "InsertFileInfoToMysql error\n");
                break;
            }

        } while (0);

        memset(filename, 0, FILE_NAME_LEN);
        memset(user, 0, USER_NAME_LEN);
        memset(md5, 0, MD5_LEN);
        memset(fileId, 0, TEMP_BUF_MAX_LEN);
        memset(fdfsFileUrl, 0, FILE_URL_LEN);

        char *out = NULL;
        if (ret == 0)
        {
            out = ReturnStatus("008");
        }
        else
        {
            out = ReturnStatus("009");
        }

        if(NULL != out)
        {
            printf(out);
            FREE(out);
        }
    }/* while(FCGI_Accept() >= 0) */
    
    return 0;
}